%{

#define _CRT_SECURE_NO_WARNINGS

#define _DEBUG_MSGS

#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <cstdio>
#include <ctype.h>
#include <stdio.h>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>

using namespace std;

#include "AbstractSyntaxTree.h"
#include "Common.h"
#include "SymbolTable.h"
#include "parser.tab.h"
#include "CodeGenerator.h"
#include "ASTChecker.h"
    

extern FILE *yyin;
extern int yylex (YYSTYPE *yylval_param, YYLTYPE *yylloc_param);

static void yyerror(const char *msg);

/*Abstract Syntax Tree*/
AbstractSyntaxTree *ast = NULL;
/*Symbol Table*/
SymbolTable *symbolTable = NULL;


%}

%defines
%locations
%pure-parser
%error-verbose

%union {		
	double number;
	char* string;
	CompOp compOp;
	LogicOp logicOp;
	ArithOp arithOp;
	int type;
	class ASTNode *astNode;
}

%token <string> TAG_NAME_START TAG_NAME_END
%token <string> TAG_VERSION_START TAG_VERSION_END
%token <string> TAG_VARIABLE_START TAG_VARIABLE_END
%token <string> TAG_RULES_START TAG_RULES_END
%token <string> TAG_RULE_START TAG_RULE_END
%token <string> TAG_IF_START TAG_IF_END TAG_THEN TAG_ELSE
%token <string> TAG_CONDITION_START TAG_CONDITION_END
%token <string> TAG_ACTION_START TAG_ACTION_END
%token <string> TAG_COMMAND_START TAG_COMMAND_END
%token <string> TAG_READ_SENSOR_START TAG_READ_SENSOR_END
%token <string> TAG_MESSAGE_START TAG_MESSAGE_END
%token <string> TAG_WRITE_ACTUATOR_START TAG_WRITE_ACTUATOR_END

%token <number> NUMBER
%token <string> IDENTIFIER
%token <arithOp> ARITH_OP
%token <string> PARENTHESES_START
%token <string> PARENTHESES_END
%token <compOp> COMP_OP
%token <logicOp> LOGIC_OP
%token <string> ASSIGNMENT_OP

%type <astNode> program
%type <astNode> name
%type <astNode> decl
%type <astNode> rules
%type <astNode> rule_set
%type <astNode> rule
%type <astNode> condition
%type <astNode> internal_condition
%type <astNode> action
%type <astNode> arith_expr
%type <astNode> commands
%type <astNode> cmd
%type <astNode> assignment
%type <astNode> read
%type <astNode> write
%type <astNode> message


%type <astNode> identifier
%type <astNode> com_op
%type <astNode> number
%type <astNode> logic_op
%type <astNode> arith_op


%start program

%%

program:
	name decl rules
	{		
		ASTNode *node = new ASTNode("program", PROGRAM, VOID, yylloc.last_line);
		
		node->AddChild($1); //name
		node->AddChild($2); //decl
		node->AddChild($3); //rules

		$$ = node;
		ast->ast = node;
	};

name: 
	TAG_NAME_START identifier TAG_NAME_END
	{
		ASTNode *node = new ASTNode("name", NAME, VOID, yylloc.last_line);	
			
		node->AddChild($2); //identifier

		$$ = node;
	}	
	| TAG_NAME_START identifier TAG_NAME_END TAG_VERSION_START number TAG_VERSION_END
	{
		ASTNode *node = new ASTNode("name", NAME, VOID, yylloc.last_line);

		node->AddChild($2); //identifier
		node->AddChild($5); //number
		
		$$ = node;
	};

decl:
	TAG_VARIABLE_START identifier number TAG_VARIABLE_END
	{		
		ASTNode *node = new ASTNode("variable", DECL, VOID, yylloc.last_line);
		
		node->AddChild($2); //identifier
		node->AddChild($3); //number
		
		$$ = node;
	}	
	| TAG_VARIABLE_START identifier number TAG_VARIABLE_END decl
	{	
	
		ASTNode *node = new ASTNode("variable", DECL, VOID, yylloc.last_line);
		
		node->AddChild($2); //identifier
		node->AddChild($3); //number
		node->AddSibling($5); //decl
		$$ = node;	
					
	};

rules:
	TAG_RULES_START rule_set TAG_RULES_END
	{
		ASTNode *node = new ASTNode("rules", RULES, VOID, yylloc.last_line);

		node->AddChild($2); //rule_set

		$$ = node;
	};

rule_set:
	TAG_RULE_START rule TAG_RULE_END
	{
		ASTNode *node = new ASTNode("rule_set", RULE_SET, VOID, yylloc.last_line);

		node->AddChild($2); //rule

		$$ = node;
	}	
	|rule_set TAG_RULE_START rule TAG_RULE_END
	{		
		$1->AddChild($3);
		$$ = $1;
	};

rule:
	TAG_IF_START condition TAG_THEN action TAG_IF_END
	{
		ASTNode *node = new ASTNode("rule", RULE, VOID, yylloc.last_line);

		node->AddChild($2); //condition
		node->AddChild($4); //action

		$$ = node;
	}
	|TAG_IF_START condition TAG_THEN action TAG_ELSE action TAG_IF_END
	{
		ASTNode *node = new ASTNode("rule", RULE, VOID, yylloc.last_line);

		node->AddChild($2); //condition
		node->AddChild($4); //action
		node->AddChild($6); //action

		$$ = node;
	}
	| action
	{
		ASTNode *node = new ASTNode("rule", RULE, VOID, yylloc.last_line);

		node->AddChild($1); //action

	}

condition:
	TAG_CONDITION_START internal_condition TAG_CONDITION_END
	{
		ASTNode *node = new ASTNode("condition", CONDITION, VOID, yylloc.last_line);

		node->AddChild($2); //internal_condition		

		$$ = node;
	};

internal_condition:
	arith_expr com_op arith_expr
	{
		ASTNode *node = new ASTNode("internal_condition", INTERNAL_CONDITION, VOID, yylloc.last_line);

		node->AddChild($1); //arith_expr
		node->AddChild($2); //com_op
		node->AddChild($3); //arith_expr

		$$ = node;
	}
	|logic_op internal_condition
	{
		LogicOp logicOp;
		$1->value.GetValue(LOGIC, &logicOp);
				
		$1->AddSibling($2);

		$$ = $1;
	}
	|internal_condition logic_op internal_condition
	{
		LogicOp logicOp;
		$2->value.GetValue(LOGIC, &logicOp);
		

		$1->AddSibling($2);
		$1->AddSibling($3);

		$$ = $1;
	}
	| PARENTHESES_START internal_condition PARENTHESES_END
	{
		ASTNode *node = new ASTNode("internal_condition", INTERNAL_CONDITION, VOID, yylloc.last_line);

		node->AddChild($2); //internal_condition

		$$ = node;
	};
	
action:
	TAG_ACTION_START commands TAG_ACTION_END
	{
		ASTNode *node = new ASTNode("action", ACTION, VOID, yylloc.last_line);
		
		node->AddChild($2); //commands
				
		$$ = node;
	};

arith_expr:
	arith_expr arith_op arith_expr
	{
		
		ASTNode *node = new ASTNode("arith_expr", ARITH_EXPR, VOID, yylloc.last_line);
		
		node->AddChild($1); //arith_expr
		node->AddChild($2); //arith_op
		node->AddChild($3); //arith_expr

		$$ = node;
	}	
	|number
	{		
		ASTNode *node = new ASTNode("arith_expr", ARITH_EXPR, VOID, yylloc.last_line);

		node->AddChild($1); //number
		$$ = node;
	}
	|identifier
	{
		ASTNode *node = new ASTNode("arith_expr", ARITH_EXPR, VOID, yylloc.last_line);

		node->AddChild($1); //identifier
		$$ = node;
	}
	|PARENTHESES_START arith_expr PARENTHESES_END
	{
		ASTNode *node = new ASTNode("arith_expr", ARITH_EXPR, VOID, yylloc.last_line);

		node->AddChild($2); //arith_expr
		$$ = node;
	};

commands:
	TAG_COMMAND_START cmd TAG_COMMAND_END
	{		
		ASTNode *node = new ASTNode("commands", COMMANDS, VOID, yylloc.last_line);
					
		node->AddChild($2); //cmd

		$$ = node;

	}
	| commands TAG_COMMAND_START cmd TAG_COMMAND_END
	{
		$1->AddChild($3); //cmd

		$$ = $1
	}
	;

cmd:
	assignment
	{
		ASTNode *node = new ASTNode("command", CMD, VOID, yylloc.last_line);

		node->AddChild($1); //assignment
		
		$$ = node;
	}
	|read
	{
		ASTNode *node = new ASTNode("command", CMD, VOID, yylloc.last_line);

		node->AddChild($1); // read
		
		$$ = node;
	}
	|write
	{
		ASTNode *node = new ASTNode("command", CMD, VOID, yylloc.last_line);

		node->AddChild($1); // write
		
		$$ = node;
	}
	|message
	{
		ASTNode *node = new ASTNode("command", CMD, VOID, yylloc.last_line);

		node->AddChild($1); //message

		$$ = node;
	};

assignment:
	identifier ASSIGNMENT_OP arith_expr
	{
		ASTNode *node = new ASTNode("assignment", ASSIGNMENT, VOID, yylloc.last_line);

		node->AddChild($1); //identifier
		node->AddChild($3); //arith_expr

		$$ = node;
	};


read:
	TAG_READ_SENSOR_START identifier TAG_READ_SENSOR_END
	{
		ASTNode *node = new ASTNode("read_sensor", READ_SENSOR, VOID, yylloc.last_line);

		node->AddChild($2); //identifier
		$$ = node;
	};

write:
	TAG_WRITE_ACTUATOR_START identifier number TAG_WRITE_ACTUATOR_END
	{
		ASTNode *node = new ASTNode("write_actuator", WRITE_ACTUATOR, VOID, yylloc.last_line);

		node->AddChild($2); //identifier
		node->AddChild($3); //number

		$$ = node;
	} 
	|TAG_WRITE_ACTUATOR_START identifier identifier TAG_WRITE_ACTUATOR_END
	{
		ASTNode *node = new ASTNode("write_actuator", WRITE_ACTUATOR, VOID, yylloc.last_line);

		node->AddChild($2); //identifier
		node->AddChild($3); //variable name

		$$ = node;
	}
	;

message:
	TAG_MESSAGE_START name decl TAG_MESSAGE_END
	{
		ASTNode *node = new ASTNode("message", MESSAGE, VOID, yylloc.last_line);

		node->AddChild($2); //name
		node->AddChild($3); //decl

		$$ = node;
	}
	|TAG_MESSAGE_START name identifier TAG_MESSAGE_END
	{
		ASTNode *node = new ASTNode("message", MESSAGE, VOID, yylloc.last_line);

		node->AddChild($2); //name
		node->AddChild($3); //identifier

		$$ = node;
	};

identifier: 
	IDENTIFIER
	{			
		ASTNode *node = new ASTNode("identifier", IDENTIFIER, STRING, yylloc.last_line);
		node->value.SetValueFromString($1);
		$$ = node;
	};

com_op:
	COMP_OP
	{
		ASTNode *node = new ASTNode("comp_op", COMP_OP, COMPARATOR, yylloc.last_line);
		node->value.SetValueFromOpComp($1);
		$$ = node;
	};

logic_op:
	LOGIC_OP
	{
		ASTNode *node = new ASTNode("logic_op", LOGIC_OP, LOGIC, yylloc.last_line);
		node->value.SetValueFromOpLogic($1);
		$$ = node;
	};

arith_op:
	ARITH_OP
	{
		ASTNode *node = new ASTNode("arith_op", ARITH_OP, ARITHMETIC, yylloc.last_line);
		node->value.SetValueFromOpArit($1);
		$$ = node;
	};

number:
	NUMBER
	{
		ASTNode *node = new ASTNode("number", NUMBER, DOUBLE, yylloc.last_line);
		node->value.SetValueFromNumber($1);		
		$$ = node;
	}
	

%%

int main (int argc, char **argv)
{
	bool verbose = false;
	std::string inputPath;
	std::string outputPath;
	
	try
	{
		std::string appName = "Agents Language Compiler";
		
		namespace po = boost::program_options;
		po::options_description desc("Options");
		desc.add_options()
			("help,h", "print help messages")
			("verbose,v", "print debug symbols")
			("input,i", po::value<std::string>()->required(),"input file to be compiled")
			("output,o", po::value<std::string>()->required(),"output c++ file");

		po::variables_map vm;

		try
		{
			po::store(po::command_line_parser(argc,argv).options(desc).run(), vm);
			//--help
			if(vm.count("help"))
			{
				std::cout << "-- Agents Language Compiler --" << endl
						  << desc << endl;
							
			}

			po::notify(vm);

		}
		catch(boost::program_options::required_option& e)
		{
			std::cerr << "ERROR: " << e.what() << std::endl;
			return -1;
		}
		catch(boost::program_options::error& e)
		{
			std::cerr << "ERROR: " << e.what() << std::endl; 
			return -1;
		}
		
		//--verbose
		if(vm.count("verbose"))
		{
			verbose = true;			
		}

		//--input
		inputPath = vm["input"].as<std::string>();
		
		//--output
		outputPath = vm["output"].as<std::string>();




		yyin = NULL;

	
		yyin = fopen(inputPath.c_str(), "r");

		if(yyin == NULL)
		{
			cout << "Unable to read input file!" <<endl;
			return -1;
		}
		else
		{
			if(verbose)
				cout << "Input file loaded." <<endl;
		}
		

		//initialize ast
		ast = new AbstractSyntaxTree();
	
		//do parse
		if(verbose)
			cout << "Parsing file" <<endl;
		yyparse(); 

		//print ast
		if(verbose)
			ast->PrintAST();

		//initialize symbol table
		symbolTable = new SymbolTable();

		//cheking AST semantics and filling symbol table
		IVisitor *checker = NULL;
		checker = new ASTChecker(symbolTable);
		if(verbose)
			cout << "Checking AST" <<endl;
		ast->ast->Accept(checker);

		if(checker != NULL)
			delete checker;
	

		//genetaring output C file
		IVisitor *cGen = NULL;
		cGen = new CodeGenerator(outputPath);
		if(verbose)
			cout << "Generating code" << endl;
		ast->ast->Accept(cGen);

		if(verbose)
			cout << "Code generated successfully!" <<endl;

		if(cGen != NULL)
			delete cGen;
	
		if(ast != NULL)
		{
			delete ast;
		}

		if(symbolTable != NULL)
		{
			delete symbolTable;
		}

		return 0;

	}
	catch(std::exception& e)
	{
		std::cerr << "Unhandled Exception: " << e.what() << std::endl;
		return -1;
	}



}


static void yyerror(const char *msg)
{
	//fprintf(stderr, "Error: line %d: %s\n", yyget_lineno(), msg);

	cout << "Error: " << msg << endl;

	exit(1);

}
